package it.gma.torero.parsing;

import it.gma.torero.parsing.token.Token;
import it.gma.torero.parsing.token.TokenFactory;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;

public class Tokenizer {

	public enum ReturnType {
		NONE, BOOLEAN, VALUE, RUNTIME, ANY;
	}

	private char[] initOperator = { '!', '&', '|', '>', '<', '=', '%' };

	private static final String[] booleanOperators = { "!", "&&", "||" , "%" , "=>" };
	private static final String[] comparationOperators = { "!=", ">", "<", ">=", "<=", "==" };

	private static final char initGroup = '(';
	private static final char finishGroup = ')';

	private int i = 0;

	private ArrayList<Token> words = new ArrayList<Token>();
	private String toTokenize;
	private Iterator<Token> iterator;

	public Tokenizer(String sequence) throws MalformedException {
		sequence = sequence.replaceAll(" ", "");
		this.toTokenize = sequence;
		tokenize();
		iterator = words.iterator();
	}

	void restart() {
		iterator = words.iterator();
	}

	private void tokenize() throws MalformedException {

		char[] chars = toTokenize.toCharArray();

		for (i = 0; i < chars.length; i++) {

			// ///////////////////// Parse a group
			if (isInitGroup(chars[i])) {

				int openingIndex = i;
				int groupOpening = 0;
				int groupClosure = 0;
				groupOpening++;
				// find the end of this group

				for (int j = i + 1; j < chars.length; j++) {
					i++;
					if (isFinishGroup(chars[j])) {
						groupClosure++;
						// Check if the group is finished
						if (groupClosure == groupOpening) {
							words.add(TokenFactory.newGroup(toTokenize.substring(openingIndex, j + 1)));
							break;
						}

					} else if (chars[j] == initGroup) {
						groupOpening++;
					}

				}

				if (groupOpening > groupClosure) {
					throw new MalformedException("bracket opening error at char " + openingIndex + " of sequence " + toTokenize);
				}

				if (i >= chars.length) {
					throw new MalformedException("bracket opening error at char " + openingIndex + " of sequence " + toTokenize);
				}

				continue;
			}

			// A group finish cannot be a first word character
			if (isFinishGroup(chars[i])) {
				throw new MalformedException("bracket closure error at char " + i + " of sequence " + toTokenize);
			}

			// Operator
			if (isInitOperator(chars[i])) {
				// check if the operator is a two character operator
				int j = i + 1;
				if (j < chars.length) {
					char[] op = { chars[i], chars[j] };
					if (isOperator(new String(op))) {
						words.add(TokenFactory.newOperator(new String(op)));
						i++;
						continue;
					}
				}

				char[] op1 = { chars[i] };
				if (isOperator(new String(op1))) {
					words.add(TokenFactory.newOperator(new String(op1)));
					continue;
				}

				continue;
			}

			// Constant
			if (isCostantDelimiter(chars[i])) {
				int openingIndex = i;
				// boolean closed = false;
				i = i + 1;
				for (int j = i; j < chars.length; j++) {
					if (isCostantDelimiter(chars[j])) {
						words.add(TokenFactory.newConstant(toTokenize.substring(openingIndex, j + 1)));
						// closed = true;
						break;
					}
					i++;
				}

				if (i >= chars.length) {
					throw new MalformedException("constant opening " + openingIndex + " of sequence " + toTokenize);
				}

				// if (!closed) {
				// throw new MalformedException("constant not closed " +
				// openingIndex + " of sequence " + toTokenize);
				// }

				continue;
			}

			// Is a word char

			if (isWordChar(chars[i])) {

				int openingIndex = i;
				boolean inner = false;
				if (i + 1 >= chars.length) {
					words.add(TokenFactory.newVariable(toTokenize.substring(openingIndex, openingIndex + 1)));
					continue;
				}

				if (!isWordChar(chars[i + 1])) {
					words.add(TokenFactory.newVariable(toTokenize.substring(openingIndex, openingIndex + 1)));
					continue;
				}

				int wordEnd = openingIndex;
				// Multi Chars word

				for (int j = i; j < chars.length; j++) {

					if (chars[j] == '[') {
						inner = true;
					} else if (chars[j] == ']') {
						inner = false;
					}

					if (!isWordChar(chars[j]) && !inner) {
						wordEnd = j;
						i = wordEnd - 1;
						break;
					} else if (j + 1 >= chars.length) {
						wordEnd = j + 1;
						i = wordEnd;
					}

				}

				// single char word

				words.add(TokenFactory.newVariable(toTokenize.substring(openingIndex, wordEnd)));

				continue;
			}

			throw new MalformedException("Illegal character at index " + i + " in expression " + toTokenize);

		}

	}

	private boolean isWordChar(char char_) {
		return !(isCostantDelimiter(char_) || isInitOperator(char_) || isGroupChar(char_));
	}

	private boolean isGroupChar(char char_) {
		return isInitGroup(char_) || isFinishGroup(char_);
	}

	private boolean isInitGroup(char char_) {
		return char_ == initGroup;
	}

	private boolean isFinishGroup(char char_) {
		return char_ == finishGroup;
	}

	private boolean isCostantDelimiter(char char_) {
		return char_ == '\'';
	}

	private boolean isOperator(String op) {
		return Arrays.asList(booleanOperators).contains(op) || Arrays.asList(comparationOperators).contains(op);
	}

	private boolean isInitOperator(char c) {

		for (char cur : initOperator) {
			if (c == cur) {
				return true;
			}
		}

		return false;

	}

	public boolean hasMoreElements() {
		return iterator.hasNext();
	}

	public Token nextElement() {
		return iterator.next();
	}

	public int countTokens() {
		return words.size();
	}

	public static class MalformedException extends Exception {

		/**
		 * 
		 */
		private static final long serialVersionUID = 8980781490745179504L;

		public MalformedException(String message) {
			super(message);

		}

	}

	@Override
	public String toString() {
		return "Tokenizer [" + toTokenize + "]";
	}

}
